package ml.practicum.feature;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import ml.practicum.logistic.BasicLV;

/**
 * A Implementation of <code>Feature</code> for LogisticValues.
 * <p> This class implements functions transforming the string inputs
 * to LogisticValue outputs. 
 * 
 * @remark In hindsight it would be nice to specify 
 * these methods in the interface. And change BasicFeature in to AbstractFeature
 * with not implemented warnings for these methods. I would give better structure
 * for the rest of the program and a better usable interface -jfs230
 * </p> 
 * @author jfs230
 * @version %I% %G%
 * @param <V>
 * 
 * TODO: Organize/rewrite rename most methods
 * FIXME: A lot of the methods here need rewriting, the getKey and processValue both
 * use different encodings of the values > processValue being the most recent
 * so getKey needs to be adjusted accordingly
 */
public class LVFeature<V extends Serializable> extends BasicFeature<V> {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1641825589839379400L;

	public LVFeature(String label) {
		super(label);
		// TODO Auto-generated constructor stub
	}
	
	
	public V getKey(BasicLV value){
		V result = null;
		if (this.getInputType() == FeatureDataType.DISCRETE){
			result = toDKey(value);
		}else{
			result = toNKey(value);
		}
		return result;
	}
	
	public V getKey(List<BasicLV> value){
		V result = null;
		if (this.getProcessTo() == FeatureDataType.DISCRETE){
			if (this.getInputType() == FeatureDataType.DISCRETE){
				result = toDKey(value);
			}else{
				result = toNKey(value);
			}
		}else{
			if (this.getInputType() == FeatureDataType.DISCRETE){
				result = toDKey(value.get(0));
			}else{
				result = toNKey(value.get(0));
			}
		}
		return result;
	}
	
	private V toDKey(BasicLV input){
		int value =((Long) Math.round((mapping.size()-1)*input.doubleValue())).intValue();
		return mapping.get(value);
	}
	
	@SuppressWarnings("unchecked")
	private V toNKey(BasicLV input){
		V result = (V) new Object();
		int value =((Long) Math.round((this.getMax())*input.doubleValue())).intValue();
		if (result instanceof String){
			return (V) ((Integer) value).toString();
		}
		return null;
	}
	
	
	@SuppressWarnings("unchecked")
	private V toNKey(List<BasicLV> input){
		//FIXME: rewrite to winner take all
		V result = (V) new Object(); 
		int value = 0;
		for(int i = 0; i< input.size(); i++){
			value += Math.round(i*input.get(i).doubleValue());
		}
		if (result instanceof String){
			return (V) ((Integer) value).toString();
		}
		return null;
	}
	
	private V toDKey(List<BasicLV> input){
		//FIXME: rewrite to winner take all
		int value = 0;
		for(int i = 0; i< input.size(); i++){
			value += Math.round(i*input.get(i).doubleValue());
		}
		return mapping.get(value);
	}
	

	
	public List<BasicLV> processValue(V input) throws IllegalArgumentException{
		List<BasicLV> result =null; 
		if (this.inputType == FeatureDataType.NUMERICAL){
			double value = 0;
			if (input instanceof Number){
				value = ((Number)input).doubleValue();
			}else 
			if (input instanceof String) {
				value = Double.parseDouble((String)input);
			}else{
				throw new IllegalArgumentException("Input can not be converted to a number");
			}
			
			if (this.processTo == FeatureDataType.NUMERICAL){
				result = toNum(value);
			}else{
				result = toDis(value);
			}
			
		}else{
			if (this.processTo == FeatureDataType.NUMERICAL){
				result = toNum(input);
			}else{
				result = toDis(input);
			}
		}
		return result;
	}
	
	private List<BasicLV> toNum(double input){
		List<BasicLV> result = new ArrayList<BasicLV>();
		double bereik = ((input-((max+min)*0.5))/((max-min)*0.5)) * Math.E;
		result.add(new BasicLV(1/(1+Math.exp(-bereik/((max-min)*0.5)))));
		return result;
	}
	
	private List<BasicLV> toDis(double input){
		List<BasicLV> result = new ArrayList<BasicLV>();
		double bereik = ((input-((max+min)*0.5))/((max-min)*0.5))* Math.E;
		double bin = 1/(1+Math.exp(-bereik))* discreteSteps;
		for(int i = 0; i <this.discreteSteps;i++){
			result.add(new BasicLV(Math.max(0, Math.min(1,bin-i))));
		}
		return result;
	}
	
	private List<BasicLV> toNum(V input){
		List<BasicLV> result = new ArrayList<BasicLV>();
		result.add(new BasicLV(this.mapping.indexOf(input)/(this.mapping.size()-1)));
		return result;
	}

	private List<BasicLV> toDis(V input){
		List<BasicLV> result = new ArrayList<BasicLV>();
		for(int i = 0;i<=this.mapping.size()-1;i++){
			BasicLV classValue = new BasicLV(0);

			if (this.mapping.indexOf(input)== i) classValue.setValue(1);
			result.add(classValue);
		}
		return result;
	}
}
